package com.focus.pay.wx.utils;

import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.SSLUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.focus.pay.core.enums.Version;
import com.focus.pay.core.exception.ExceptionEnum;
import com.focus.pay.core.exception.PayException;
import com.focus.pay.core.utils.StringUtils;
import com.focus.pay.wx.config.WxPayConfig;
import com.focus.pay.wx.constant.WxPayConstant;
import com.focus.pay.wx.model.order.model.WxPayUnifiedResponse;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;

public class WxPayUtils {

    private static final Log log = LogFactory.get();
    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final Random RANDOM = new SecureRandom();
    private static final String prefix = "\n";

    /**
     * MD5加密
     * @param str 待加密字符串
     */
    public static String MD5Encryption(String str) {
        return DigestUtil.md5Hex(str);
    }

    /**
     * HMAC-SHA256加密
     * @param str 待加密字符串
     */
    public static String HMACSHA256Encryption(String str,String key){
        HMac hmac = new HMac(HmacAlgorithm.HmacSHA256, key.getBytes());
        return hmac.digestHex(str);
    }

    /**
     * SHA256-RSA加密
     * @param str 待加密字符串
     * @param key RSA-私钥
     */
    public static String SHA256RSAEncryption(String str,String key){
        Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA,key,null);
        return sign.signHex(str);
    }

    /**
     * BEAN转XML
     * @param obj
     * @return
     */
    public static String beanToXmlStr(Object obj) {
        log.info("对象转换XML-STAR：{}", JSONUtil.toJsonStr(obj));
        String xmlStr = XmlUtil.beanToXml(obj).getXmlEncoding();
        log.info("对象转换XML-END：{}", xmlStr);
        return xmlStr;
    }

    /**
     * 获取随机字符串 Nonce Str
     * @return String 随机字符串
     */
    public static String buildNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }

    /**
     * 组装签名字符串
     * @param paramMap
     * @return
     */
    public static String buildSignStr(Map<String,String> paramMap, String key, Version version) throws PayException {
        //微信支付2.0
        if(Version.VERSION_2== version){
            //排序
            List<Map.Entry<String, String>> sortList = new ArrayList<Map.Entry<String, String>>(paramMap.entrySet());
            Collections.sort(sortList, new Comparator<Map.Entry<String, String>>() {
                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                    return  o1.getKey().compareToIgnoreCase(o2.getKey());
                }
            });
            //组装参数
            return sortList.stream().map(x -> x.getKey()+"="+x.getValue()).collect(Collectors.joining("&"))+"&key="+key;
        //微信支付3.0
        }else if(Version.VERSION_3== version){
            return paramMap.values().stream().collect(Collectors.joining(prefix));
        }else {
            throw new PayException(ExceptionEnum.VERSION_ERROR);
        }
    }

    /**
     * 构建SSL-Factory
     * @param wxPayConfig
     * @throws Exception
     */
    public static SSLSocketFactory buildSSLFactory(WxPayConfig wxPayConfig) throws PayException {
        try {
            Assert.notEmpty(wxPayConfig.getCertPath());
            //证书
            char[] password = wxPayConfig.getCertPassword().toCharArray();
            InputStream certStream = new ByteArrayInputStream(ResourceUtil.readBytes(wxPayConfig.getCertPath()));
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(certStream, password);

            //实例化密钥库 & 初始化密钥工厂
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, password);

            // 创建 SSLContext
            SSLContext sslContext = SSLUtil.createSSLContext("TLSv1", kmf.getKeyManagers(), null);
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
            throw new PayException(ExceptionEnum.SSL_ERROR);
        }
    }

    /**
     * 唤起JSAPI支付参数
     * @param <T> 返回实体类
     * @param wxPayConfig
     * @return
     */
    public static <T extends WxPayUnifiedResponse> Map<String,String> JSAPIRecall(T t, WxPayConfig wxPayConfig, Version version) throws PayException {
        Map<String, String> packageParams = new LinkedHashMap<>();
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000L);
        String nonceStr = buildNonceStr();
        //微信支付2.0
        if(Version.VERSION_2== version){
            String packageStr = "prepay_id=" + t.getPrepay_id();

            packageParams.put("appId", wxPayConfig.getAppId());
            packageParams.put("timeStamp", timeStamp);
            packageParams.put("nonceStr", nonceStr);
            packageParams.put("package", packageStr);
            packageParams.put("signType", StringUtils.isEmpty(wxPayConfig.getSignType()) ?
                    WxPayConstant.WxPayEncryptionType.MD5.toString() :
                    wxPayConfig.getSignType().toUpperCase());

            String signStr =buildSignStr(packageParams, wxPayConfig.getKey(), version);

            if(WxPayConstant.WxPayEncryptionType.MD5.toString().equals(wxPayConfig.getSignType().toUpperCase())){
                packageParams.put("paySign", WxPayUtils.MD5Encryption(signStr));
            }
            else if(WxPayConstant.WxPayEncryptionType.HMACSHA256.equals(wxPayConfig.getSignType().replace("-","").toUpperCase())){
                packageParams.put("paySign", WxPayUtils.HMACSHA256Encryption(signStr,wxPayConfig.getKey()));
            }else{
                throw new PayException(ExceptionEnum.SIGN_ERROR);
            }

        //微信支付3.0
        }else if(Version.VERSION_3== version){
            String packageStr = "prepay_id=" + t.getPrepay_id();
            packageParams.put("appId", wxPayConfig.getAppId());
            packageParams.put("timeStamp", timeStamp);
            packageParams.put("nonceStr", nonceStr);
            packageParams.put("package", packageStr);
            String signStr =buildSignStr(packageParams, wxPayConfig.getKey(), version);
            packageParams.put("paySign",WxPayUtils.SHA256RSAEncryption(signStr, wxPayConfig.getKey()));
            packageParams.put("signType","RSA");
        }else{
            throw new PayException(ExceptionEnum.VERSION_ERROR);
        }
        return packageParams;
    }

    /**
     * 唤起APP支付参数
     * @param <T> 返回实体类
     * @param wxPayConfig
     * @return
     */
    public static <T extends WxPayUnifiedResponse> Map<String,String> AppRecall(T t, WxPayConfig wxPayConfig, Version version) throws PayException {
        Map<String, String> packageParams = new LinkedHashMap<>();
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000L);
        String nonceStr = buildNonceStr();

        //微信支付2.0
        if(Version.VERSION_2== version){
            packageParams.put("appId", wxPayConfig.getAppId());
            packageParams.put("partnerid",wxPayConfig.getMchId());
            packageParams.put("prepayid",t.getPrepay_id());
            packageParams.put("package","Sign=WXPay");
            packageParams.put("nonceStr",nonceStr);
            packageParams.put("timestamp",timeStamp);

            String signStr =buildSignStr(packageParams, wxPayConfig.getKey(), version);
            if(WxPayConstant.WxPayEncryptionType.MD5.toString().equals(wxPayConfig.getSignType().toUpperCase())){
                packageParams.put("paySign", WxPayUtils.MD5Encryption(signStr));
            }
            else if(WxPayConstant.WxPayEncryptionType.HMACSHA256.equals(wxPayConfig.getSignType().replace("-","").toUpperCase())){
                packageParams.put("paySign", WxPayUtils.HMACSHA256Encryption(signStr,wxPayConfig.getKey()));
            }else{
                throw new PayException(ExceptionEnum.SIGN_ERROR);
            }
        //微信支付3.0
        }else if (Version.VERSION_3== version){
            packageParams.put("appId", wxPayConfig.getAppId());
            packageParams.put("timestamp",timeStamp);
            packageParams.put("nonceStr",nonceStr);
            packageParams.put("prepayid",t.getPrepay_id());
            String signStr =buildSignStr(packageParams, wxPayConfig.getKey(), version);
            packageParams.put("paySign",WxPayUtils.SHA256RSAEncryption(signStr, wxPayConfig.getKey()));
            packageParams.put("package","Sign=WXPay");
            packageParams.put("partnerid",wxPayConfig.getMchId());
        }else {
            throw new PayException(ExceptionEnum.VERSION_ERROR);
        }
        return packageParams;
    }





}
